#!/bin/bash
###############################################################################
#       脚本名称: ini.sh
#       功能描述: INI文件操作模块，提供.ini/.conf/.cfg文件的读取、写入、删除等功能。
#       作者: 林再来
#       日期: 2025-10-14
#       版本: 1.0
#       功能:
#           1、INI文件读取、写入、删除操作
#           2、INI文件格式验证
#           3、段落(section)和键值对管理
#           4、INI文件合并和格式化
#           5、INI文件变更检测和监控
#           6、注释保留和处理
#       依赖(插件/命令):
#           1、grep
#           2、sed
#           3、md5sum或cksum
###############################################################################

# 防止重复加载
if [[ -n "${ini_sh_loaded:-}" ]]; then
    return 0
fi
readonly ini_sh_loaded=1

# 注意：不使用严格模式，避免影响交互式 shell

# 获取脚本目录和项目根目录（如果尚未定义）
if [[ -z "${script_dir:-}" ]]; then
    script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    readonly script_dir
fi

if [[ -z "${project_root:-}" ]]; then
    project_root="$(cd "${script_dir}/../.." && pwd)"
    readonly project_root
fi

###############################################################################
# 基础操作函数
###############################################################################

# 函数: 验证INI文件格式
# 参数: $1 - INI文件路径
# 返回: 0-格式正确, 1-格式错误
function ini_validate() {
    local ini_file="$1"
    
    if [[ -z "$ini_file" ]]; then
        log_error "ini" "错误: 缺少参数: INI文件路径"
        return 1
    fi
    
    if [[ ! -f "$ini_file" ]]; then
        log_error "ini" "错误: INI文件不存在: $ini_file"
        return 1
    fi
    
    log_debug "ini" "验证INI文件格式: $ini_file"
    
    # INI文件格式检查
    local line_num=0
    local has_error=false
    local in_section=false
    
    while IFS= read -r line; do
        ((line_num++))
        
        # 跳过空行
        [[ -z "$line" ]] && continue
        
        # 跳过注释行（# 或 ; 开头）
        [[ "$line" =~ ^[[:space:]]*[\#\;] ]] && continue
        
        # 检查段落标记 [section]
        if [[ "$line" =~ ^\[.*\]$ ]]; then
            in_section=true
            # 检查段落名是否合法
            local section_name="${line#\[}"
            section_name="${section_name%\]}"
            if [[ -z "$section_name" ]]; then
                log_warn "ini" "第${line_num}行: 空段落名"
                has_error=true
            fi
            continue
        fi
        
        # 检查键值对格式: key=value 或 key: value
        if [[ "$line" =~ ^[[:space:]]*[^=:]+[=:].* ]]; then
            continue
        fi
        
        # 其他情况视为格式错误
        log_warn "ini" "第${line_num}行格式可能有误: $line"
        has_error=true
    done < "$ini_file"
    
    if [[ "$has_error" == "true" ]]; then
        log_warn "ini" "INI文件包含格式警告，但不影响解析"
    fi
    
    log_debug "ini" "INI文件格式验证完成: $ini_file"
    return 0
}

# 函数: 读取INI文件中的值
# 参数: $1 - INI文件路径, $2 - 段落名, $3 - 键名称
# 返回: 0-成功, 1-失败
function ini_read() {
    local ini_file="$1"
    local section="$2"
    local key="$3"
    
    if [[ -z "$ini_file" ]] || [[ -z "$section" ]] || [[ -z "$key" ]]; then
        log_error "ini" "错误: 缺少参数: INI文件路径、段落名或键"
        return 1
    fi
    
    if [[ ! -f "$ini_file" ]]; then
        log_error "ini" "错误: INI文件不存在: $ini_file"
        return 1
    fi
    
    log_debug "ini" "读取INI值: $ini_file -> [$section] $key"
    
    # 使用awk读取指定section下的key值
    local value
    value=$(awk -F '=' -v section="$section" -v key="$key" '
        BEGIN { in_section = 0; found = 0 }
        
        # 去除首尾空格
        { gsub(/^[[:space:]]+|[[:space:]]+$/, "", $0) }
        
        # 跳过空行和注释
        /^[[:space:]]*$/ { next }
        /^[[:space:]]*[#;]/ { next }
        
        # 匹配段落
        /^\[.*\]$/ {
            # 提取段落名
            current_section = $0
            gsub(/^\[|\]$/, "", current_section)
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", current_section)
            
            if (current_section == section) {
                in_section = 1
            } else {
                in_section = 0
            }
            next
        }
        
        # 在目标段落中查找键
        in_section == 1 && /[=:]/ {
            # 提取键和值（支持 = 和 : 两种分隔符）
            if ($0 ~ /=/) {
                split($0, kv, "=")
            } else {
                split($0, kv, ":")
            }
            
            current_key = kv[1]
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", current_key)
            
            if (current_key == key) {
                # 提取值部分
                value_part = substr($0, index($0, kv[2]))
                gsub(/^[[:space:]]+|[[:space:]]+$/, "", value_part)
                
                # 去除引号
                gsub(/^["'\'']|["'\'']$/, "", value_part)
                
                print value_part
                found = 1
                exit
            }
        }
    ' "$ini_file")
    
    # 返回值（可能为空）
    echo "$value"
    return 0
}

# 函数: 写入值到INI文件
# 参数: $1 - INI文件路径, $2 - 段落名, $3 - 键名称, $4 - 值
# 返回: 0-成功, 1-失败
function ini_write() {
    local ini_file="$1"
    local section="$2"
    local key="$3"
    local value="$4"
    
    if [[ -z "$ini_file" ]] || [[ -z "$section" ]] || [[ -z "$key" ]]; then
        log_error "ini" "错误: 缺少参数: INI文件路径、段落名或键"
        return 1
    fi
    
    # 如果文件不存在，创建
    if [[ ! -f "$ini_file" ]]; then
        log_info "ini" "INI文件不存在，创建新文件: $ini_file"
        touch "$ini_file" || {
            log_error "ini" "错误: 无法创建INI文件: $ini_file"
            return 1
        }
    fi
    
    log_debug "ini" "写入INI值: $ini_file -> [$section] $key = $value"
    
    # 备份原文件
    local backup_file="${ini_file}.bak.$(date +%s)"
    cp "$ini_file" "$backup_file" || {
        log_error "ini" "错误: 无法创建备份文件: $backup_file"
        return 1
    }
    
    # 处理值中的特殊字符（如果包含空格或特殊字符，加引号）
    local quoted_value="$value"
    if [[ "$value" =~ [[:space:]] ]] || [[ "$value" =~ [\#\;] ]]; then
        quoted_value="\"$value\""
    fi
    
    # 使用awk处理INI文件写入
    awk -F '=' -v section="$section" -v key="$key" -v value="$quoted_value" '
        BEGIN {
            in_section = 0
            section_found = 0
            key_found = 0
            section_ended = 0
        }
        
        # 跳过空行但保留
        /^[[:space:]]*$/ {
            if (in_section == 1 && key_found == 0 && section_ended == 0) {
                # 在目标段落中但还没找到键，可能是段落结束
                section_ended = 1
            }
            print
            next
        }
        
        # 匹配段落
        /^\[.*\]$/ {
            # 如果在目标段落中但还没找到键，在段落结束前添加
            if (in_section == 1 && key_found == 0) {
                print key "=" value
                key_found = 1
            }
            
            # 提取段落名
            current_section = $0
            gsub(/^\[|\]$/, "", current_section)
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", current_section)
            
            if (current_section == section) {
                in_section = 1
                section_found = 1
                section_ended = 0
            } else {
                in_section = 0
            }
            
            print
            next
        }
        
        # 在目标段落中处理键值对
        in_section == 1 && /[=:]/ {
            # 提取键名
            if ($0 ~ /=/) {
                split($0, kv, "=")
            } else {
                split($0, kv, ":")
            }
            
            current_key = kv[1]
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", current_key)
            
            if (current_key == key) {
                # 找到键，替换值
                print key "=" value
                key_found = 1
                next
            }
        }
        
        # 默认保留原行
        { print }
        
        END {
            # 如果段落存在但键不存在，已在段落切换时添加
            # 如果段落不存在，需要在文件末尾添加段落和键
            if (section_found == 0) {
                print ""
                print "[" section "]"
                print key "=" value
            } else if (key_found == 0) {
                # 段落存在但键不存在，且到文件末尾都在该段落中
                print key "=" value
            }
        }
    ' "$ini_file" > "${ini_file}.tmp"
    
    # 替换原文件
    mv "${ini_file}.tmp" "$ini_file"
    
    # 验证修改
    if ini_validate "$ini_file"; then
        rm -f "$backup_file"
        log_debug "ini" "INI值写入成功"
        return 0
    else
        log_error "ini" "错误: 写入后INI文件格式错误，恢复备份"
        mv "$backup_file" "$ini_file"
        return 1
    fi
}

# 函数: 删除INI文件中的键
# 参数: $1 - INI文件路径, $2 - 段落名, $3 - 键名称
# 返回: 0-成功, 1-失败
function ini_delete() {
    local ini_file="$1"
    local section="$2"
    local key="$3"
    
    if [[ -z "$ini_file" ]] || [[ -z "$section" ]] || [[ -z "$key" ]]; then
        log_error "ini" "错误: 缺少参数: INI文件路径、段落名或键"
        return 1
    fi
    
    if [[ ! -f "$ini_file" ]]; then
        log_error "ini" "错误: INI文件不存在: $ini_file"
        return 1
    fi
    
    log_debug "ini" "删除INI键: $ini_file -> [$section] $key"
    
    # 备份原文件
    local backup_file="${ini_file}.bak.$(date +%s)"
    cp "$ini_file" "$backup_file" || {
        log_error "ini" "错误: 无法创建备份文件: $backup_file"
        return 1
    }
    
    # 使用awk删除指定键
    awk -F '=' -v section="$section" -v key="$key" '
        BEGIN { in_section = 0 }
        
        # 匹配段落
        /^\[.*\]$/ {
            current_section = $0
            gsub(/^\[|\]$/, "", current_section)
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", current_section)
            
            if (current_section == section) {
                in_section = 1
            } else {
                in_section = 0
            }
            print
            next
        }
        
        # 在目标段落中删除键
        in_section == 1 && /[=:]/ {
            if ($0 ~ /=/) {
                split($0, kv, "=")
            } else {
                split($0, kv, ":")
            }
            
            current_key = kv[1]
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", current_key)
            
            if (current_key == key) {
                # 找到键，跳过此行（删除）
                next
            }
        }
        
        # 默认保留原行
        { print }
    ' "$ini_file" > "${ini_file}.tmp"
    
    mv "${ini_file}.tmp" "$ini_file"
    
    # 验证修改
    if ini_validate "$ini_file"; then
        rm -f "$backup_file"
        log_debug "ini" "INI键删除成功"
        return 0
    else
        log_error "ini" "错误: 删除后INI文件格式错误，恢复备份"
        mv "$backup_file" "$ini_file"
        return 1
    fi
}

# 函数: 列出INI文件中的所有段落
# 参数: $1 - INI文件路径
# 返回: 0-成功, 1-失败
function ini_list_sections() {
    local ini_file="$1"
    
    if [[ -z "$ini_file" ]]; then
        log_error "ini" "错误: 缺少参数: INI文件路径"
        return 1
    fi
    
    if [[ ! -f "$ini_file" ]]; then
        log_error "ini" "错误: INI文件不存在: $ini_file"
        return 1
    fi
    
    log_debug "ini" "列出INI段落: $ini_file"
    
    # 提取所有段落名
    grep -E '^\[.*\]$' "$ini_file" 2>/dev/null | \
    sed -E 's/^\[|\]$//g' | \
    sed -E 's/^[[:space:]]+|[[:space:]]+$//' | \
    sort -u
    
    return 0
}

# 函数: 列出INI文件中指定段落的所有键
# 参数: $1 - INI文件路径, $2 - 段落名(可选，不指定则列出所有键)
# 返回: 0-成功, 1-失败
function ini_list_keys() {
    local ini_file="$1"
    local section="${2:-}"
    
    if [[ -z "$ini_file" ]]; then
        log_error "ini" "错误: 缺少参数: INI文件路径"
        return 1
    fi
    
    if [[ ! -f "$ini_file" ]]; then
        log_error "ini" "错误: INI文件不存在: $ini_file"
        return 1
    fi
    
    if [[ -z "$section" ]]; then
        log_debug "ini" "列出所有INI键: $ini_file"
    else
        log_debug "ini" "列出INI键: $ini_file -> [$section]"
    fi
    
    # 使用awk提取键名
    awk -F '=' -v target_section="$section" '
        BEGIN { in_section = 0; list_all = (target_section == "") }
        
        # 跳过空行和注释
        /^[[:space:]]*$/ { next }
        /^[[:space:]]*[#;]/ { next }
        
        # 匹配段落
        /^\[.*\]$/ {
            current_section = $0
            gsub(/^\[|\]$/, "", current_section)
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", current_section)
            
            if (list_all || current_section == target_section) {
                in_section = 1
            } else {
                in_section = 0
            }
            next
        }
        
        # 在目标段落或列出所有时提取键
        (list_all || in_section == 1) && /[=:]/ {
            if ($0 ~ /=/) {
                split($0, kv, "=")
            } else {
                split($0, kv, ":")
            }
            
            key = kv[1]
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", key)
            
            if (list_all) {
                print current_section "." key
            } else {
                print key
            }
        }
    ' "$ini_file" | sort -u
    
    return 0
}

###############################################################################
# 文件操作函数
###############################################################################

# 函数: 合并INI文件
# 参数: $1 - 目标文件, $2 - 源文件, $3 - 合并模式(可选: merge/overwrite)
# 返回: 0-成功, 1-失败
function ini_merge() {
    local target_file="$1"
    local source_file="$2"
    local merge_mode="${3:-merge}"
    
    if [[ -z "$target_file" ]] || [[ -z "$source_file" ]]; then
        log_error "ini" "错误: 缺少参数: 目标文件和源文件路径"
        return 1
    fi
    
    if [[ ! -f "$source_file" ]]; then
        log_error "ini" "错误: 源文件不存在: $source_file"
        return 1
    fi
    
    # 如果目标文件不存在，直接复制源文件
    if [[ ! -f "$target_file" ]]; then
        log_info "ini" "目标文件不存在，复制源文件: $target_file"
        cp "$source_file" "$target_file"
        return $?
    fi
    
    log_debug "ini" "合并INI文件: $source_file -> $target_file (模式: $merge_mode)"
    
    # 备份目标文件
    local backup_file="${target_file}.bak.$(date +%s)"
    cp "$target_file" "$backup_file" || {
        log_error "ini" "错误: 无法创建备份文件: $backup_file"
        return 1
    }
    # 根据合并模式处理
    case "$merge_mode" in
        "overwrite")
            # 覆盖模式：源文件的值覆盖目标文件
            # 读取源文件的所有段落
            local sections
            sections=$(ini_list_sections "$source_file")
            
            while IFS= read -r section; do
                [[ -z "$section" ]] && continue
                
                # 读取该段落的所有键
                local keys
                keys=$(ini_list_keys "$source_file" "$section")
                
                while IFS= read -r key; do
                    [[ -z "$key" ]] && continue
                    
                    # 读取值
                    local value
                    value=$(ini_read "$source_file" "$section" "$key")
                    
                    # 写入目标文件
                    if ! ini_write "$target_file" "$section" "$key" "$value"; then
                        log_error "ini" "合并失败，恢复备份"
                        mv "$backup_file" "$target_file"
                        return 1
                    fi
                done <<< "$keys"
            done <<< "$sections"
            ;;
        "merge"|*)
            # 合并模式：只添加目标文件中不存在的键
            local sections
            sections=$(ini_list_sections "$source_file")
            
            while IFS= read -r section; do
                [[ -z "$section" ]] && continue
                
                local keys
                keys=$(ini_list_keys "$source_file" "$section")
                
                while IFS= read -r key; do
                    [[ -z "$key" ]] && continue
                    
                    # 检查目标文件中是否已存在该键
                    local existing_value
                    existing_value=$(ini_read "$target_file" "$section" "$key" 2>/dev/null)
                    
                    if [[ -z "$existing_value" ]]; then
                        # 不存在，添加
                        local value
                        value=$(ini_read "$source_file" "$section" "$key")
                        
                        if ! ini_write "$target_file" "$section" "$key" "$value"; then
                            log_error "ini" "合并失败，恢复备份"
                            mv "$backup_file" "$target_file"
                            return 1
                        fi
                    fi
                done <<< "$keys"
            done <<< "$sections"
            ;;
    esac
    
    # 验证结果
    if ini_validate "$target_file"; then
        rm -f "$backup_file"
        log_debug "ini" "INI文件合并成功"
        return 0
    else
        log_error "ini" "错误: 合并后INI文件格式错误，恢复备份"
        mv "$backup_file" "$target_file"
        return 1
    fi
}

# 函数: 格式化INI文件
# 参数: $1 - INI文件路径
# 返回: 0-成功, 1-失败
function ini_format() {
    local ini_file="$1"
    
    if [[ -z "$ini_file" ]]; then
        log_error "ini" "错误: 缺少参数: INI文件路径"
        return 1
    fi
    
    if [[ ! -f "$ini_file" ]]; then
        log_error "ini" "错误: INI文件不存在: $ini_file"
        return 1
    fi
    
    log_debug "ini" "格式化INI文件: $ini_file"
    
    # 备份原文件
    local backup_file="${ini_file}.bak.$(date +%s)"
    cp "$ini_file" "$backup_file" || {
        log_error "ini" "错误: 无法创建备份文件: $backup_file"
        return 1
    }
    
    # 格式化处理
    awk '
        BEGIN { prev_was_section = 0; first_line = 1 }
        
        # 跳过空行
        /^[[:space:]]*$/ {
            # 在段落前后保留一个空行
            if (prev_was_section == 0 && first_line == 0) {
                empty_line_pending = 1
            }
            next
        }
        
        # 注释行
        /^[[:space:]]*[#;]/ {
            if (empty_line_pending == 1 && first_line == 0) {
                print ""
                empty_line_pending = 0
            }
            print
            first_line = 0
            next
        }
        
        # 段落标记
        /^\[.*\]$/ {
            if (empty_line_pending == 1 && first_line == 0) {
                print ""
                empty_line_pending = 0
            }
            if (first_line == 0) {
                print ""
            }
            print
            prev_was_section = 1
            first_line = 0
            empty_line_pending = 0
            next
        }
        
        # 键值对
        /[=:]/ {
            if (empty_line_pending == 1 && first_line == 0) {
                print ""
                empty_line_pending = 0
            }
            # 标准化键值对格式
            if ($0 ~ /=/) {
                split($0, kv, "=")
                key = kv[1]
                value = substr($0, index($0, kv[2]))
            } else {
                split($0, kv, ":")
                key = kv[1]
                value = substr($0, index($0, kv[2]))
            }
            
            # 去除首尾空格
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", key)
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
            
            print key "=" value
            prev_was_section = 0
            first_line = 0
            next
        }
        
        # 其他行保留
        {
            if (empty_line_pending == 1 && first_line == 0) {
                print ""
                empty_line_pending = 0
            }
            print
            first_line = 0
        }
    ' "$ini_file" > "${ini_file}.tmp"
    
    # 确保文件末尾有换行符
    if [[ -s "${ini_file}.tmp" ]]; then
        if [[ "$(tail -c1 "${ini_file}.tmp")" != "" ]]; then
            echo "" >> "${ini_file}.tmp"
        fi
    fi
    
    mv "${ini_file}.tmp" "$ini_file"
    
    # 验证结果
    if ini_validate "$ini_file"; then
        rm -f "$backup_file"
        log_debug "ini" "INI文件格式化成功"
        return 0
    else
        log_error "ini" "错误: 格式化后INI文件格式错误，恢复备份"
        mv "$backup_file" "$ini_file"
        return 1
    fi
}

###############################################################################
# 变更检测函数
###############################################################################

# 函数: 计算INI文件哈希值
# 参数: $1 - INI文件路径, $@ - 要计算哈希的键数组(可选，格式: section.key)
# 返回: 哈希值字符串
function ini_get_hash() {
    local ini_file="$1"
    shift
    local keys=("$@")
    
    if [[ -z "$ini_file" ]]; then
        log_error "ini" "错误: 缺少参数: INI文件路径"
        return 1
    fi
    
    if [[ ! -f "$ini_file" ]]; then
        log_error "ini" "错误: INI文件不存在: $ini_file"
        return 1
    fi
    
    log_debug "ini" "计算INI文件哈希值: $ini_file"
    
    local content=""
    
    if [[ ${#keys[@]} -eq 0 ]]; then
        # 如果没有指定键，计算整个文件的内容哈希（排除注释和空行）
        content=$(grep -vE '^[[:space:]]*$|^[[:space:]]*[#;]' "$ini_file" 2>/dev/null | sort)
    else
        # 计算指定键的值的哈希
        for key_path in "${keys[@]}"; do
            # 分割 section.key
            local section="${key_path%%.*}"
            local key="${key_path#*.}"
            
            if [[ "$section" == "$key_path" ]]; then
                log_warn "ini" "键路径格式错误，应为 section.key: $key_path"
                continue
            fi
            
            local value
            value=$(ini_read "$ini_file" "$section" "$key" 2>/dev/null)
            content="${content}${section}.${key}=${value}"
        done
    fi
    
    # 计算哈希值(三级降级策略)
    if command -v md5sum >/dev/null 2>&1; then
        echo -n "$content" | md5sum | cut -d' ' -f1
    elif command -v cksum >/dev/null 2>&1; then
        echo -n "$content" | cksum | cut -d' ' -f1
    else
        # 简单的字符串长度和首尾字符作为备用哈希
        local first_char=""
        local last_char=""
        if [[ -n "$content" ]]; then
            first_char=$(echo -n "$content" | head -c1)
            last_char=$(echo -n "$content" | tail -c1)
        fi
        echo -n "${#content}-${first_char}-${last_char}"
    fi
}

# 函数: 保存INI文件哈希值到文件
# 参数: $1 - INI文件路径, $2 - 哈希文件路径(可选), $@ - 要保存哈希的键数组(可选)
# 返回: 0-成功, 1-失败
function ini_save_hash() {
    local ini_file="$1"
    local hash_file="${2:-}"
    shift 2
    local keys=("$@")
    
    if [[ -z "$ini_file" ]]; then
        log_error "ini" "错误: 缺少参数: INI文件路径"
        return 1
    fi
    
    # 如果没有指定哈希文件，使用默认路径
    if [[ -z "$hash_file" ]]; then
        hash_file="${ini_file}.hash"
    fi
    
    log_debug "ini" "保存INI哈希值: $ini_file -> $hash_file"
    
    # 计算哈希值
    local hash_value
    if [[ ${#keys[@]} -eq 0 ]]; then
        hash_value=$(ini_get_hash "$ini_file")
    else
        hash_value=$(ini_get_hash "$ini_file" "${keys[@]}")
    fi
    
    if [[ $? -ne 0 ]] || [[ -z "$hash_value" ]]; then
        log_error "ini" "错误: 计算哈希值失败"
        return 1
    fi
    
    # 保存哈希值到文件
    if echo "$hash_value" > "$hash_file"; then
        log_debug "ini" "哈希值已保存: $hash_value"
        return 0
    else
        log_error "ini" "错误: 保存哈希值失败: $hash_file"
        return 1
    fi
}

# 函数: 检查INI文件是否发生变更
# 参数: $1 - INI文件路径, $2 - 哈希文件路径(可选), $@ - 要检查的键数组(可选)
# 返回: 0-发生变更, 1-未发生变更
function ini_check_changed() {
    local ini_file="$1"
    local hash_file="${2:-}"
    shift 2
    local keys=("$@")
    
    if [[ -z "$ini_file" ]]; then
        log_error "ini" "错误: 缺少参数: INI文件路径"
        return 1
    fi
    
    if [[ ! -f "$ini_file" ]]; then
        log_error "ini" "错误: INI文件不存在: $ini_file"
        return 1
    fi
    
    # 如果没有指定哈希文件，使用默认路径
    if [[ -z "$hash_file" ]]; then
        hash_file="${ini_file}.hash"
    fi
    
    log_debug "ini" "检查INI文件变更: $ini_file"
    
    # 如果哈希文件不存在，说明是第一次检查，视为已变更
    if [[ ! -f "$hash_file" ]]; then
        log_debug "ini" "哈希文件不存在，视为已变更: $hash_file"
        return 0
    fi
    
    # 读取保存的哈希值
    local saved_hash
    saved_hash=$(cat "$hash_file" 2>/dev/null | head -1)
    if [[ -z "$saved_hash" ]]; then
        log_debug "ini" "无法读取保存的哈希值，视为已变更"
        return 0
    fi
    
    # 计算当前哈希值
    local current_hash
    if [[ ${#keys[@]} -eq 0 ]]; then
        current_hash=$(ini_get_hash "$ini_file")
    else
        current_hash=$(ini_get_hash "$ini_file" "${keys[@]}")
    fi
    
    if [[ $? -ne 0 ]] || [[ -z "$current_hash" ]]; then
        log_error "ini" "错误: 计算当前哈希值失败"
        return 1
    fi
    
    # 比较哈希值
    if [[ "$current_hash" != "$saved_hash" ]]; then
        log_debug "ini" "检测到配置变更 (当前: $current_hash, 保存: $saved_hash)"
        return 0
    else
        log_debug "ini" "配置未发生变更"
        return 1
    fi
}

###############################################################################
# 帮助函数
###############################################################################

# 函数: 显示帮助信息
function ini_help() {
    cat << 'EOF'
INI文件操作工具 (ini.sh) - 使用说明

功能函数：
  ini_validate <ini_file>                    - 验证INI文件格式
  ini_read <ini_file> <section> <key>        - 读取INI值
  ini_write <ini_file> <section> <key> <value> - 写入INI值
  ini_delete <ini_file> <section> <key>      - 删除INI键
  ini_list_sections <ini_file>               - 列出所有段落
  ini_list_keys <ini_file> [section]         - 列出键（可指定段落）
  ini_merge <target> <source> [mode]         - 合并INI文件
  ini_format <ini_file>                      - 格式化INI文件

变更检测函数：
  ini_get_hash <ini_file> [keys...]              - 计算INI文件哈希值
  ini_save_hash <ini_file> [hash_file] [keys...] - 保存哈希值到文件
  ini_check_changed <ini_file> [hash_file] [keys...] - 检查是否变更

参数说明：
  ini_file - INI文件路径
  section  - 段落名称
  key      - 键名称
  value    - 要设置的值
  keys     - 键数组，格式为 section.key，多个用空格分隔
  mode     - 合并模式：merge(默认，仅添加新键)|overwrite(覆盖已存在的键)
  hash_file - 哈希文件路径

INI文件格式：
  [section]              # 段落标记
  key=value              # 键值对（使用=）
  key: value             # 键值对（使用:）
  key="value"            # 带引号的值
  # comment              # 注释（#开头）
  ; comment              # 注释（;开头）
  
  注意事项：
  - 段落名用方括号包裹
  - 键值对可以使用 = 或 : 分隔
  - 值如果包含空格或特殊字符，会自动加引号
  - 支持 # 和 ; 两种注释风格

使用示例：
  # 基本操作
  ini_read app.ini "database" "host"
  ini_write app.ini "database" "port" "3306"
  ini_delete app.ini "database" "old_key"
  
  # 列出段落和键
  ini_list_sections app.ini
  ini_list_keys app.ini "database"
  
  # 合并INI文件
  ini_merge prod.ini dev.ini "merge"
  
  # 格式化INI文件
  ini_format app.ini
  
  # 变更检测
  ini_get_hash app.ini "database.host" "database.port"
  ini_save_hash app.ini app.hash
  ini_check_changed app.ini app.hash

EOF
}

###############################################################################
# 简单日志函数（如果外部日志模块不可用）
###############################################################################

if ! command -v log_debug >/dev/null 2>&1; then
    function log_debug() {
        local module="${1:-}"
        shift
        echo "[DEBUG] [$module] $*" >&2
    }
fi

if ! command -v log_info >/dev/null 2>&1; then
    function log_info() {
        local module="${1:-}"
        shift
        echo "[INFO]  [$module] $*" >&2
    }
fi

if ! command -v log_warn >/dev/null 2>&1; then
    function log_warn() {
        local module="${1:-}"
        shift
        echo "[WARN]  [$module] $*" >&2
    }
fi

if ! command -v log_error >/dev/null 2>&1; then
    function log_error() {
        local module="${1:-}"
        shift
        echo "[ERROR] [$module] $*" >&2
    }
fi

